package org.anachronos.jeep.agent.instrumentation;

import java.util.regex.Matcher;

import org.ow2.util.asm.ClassAdapter;
import org.ow2.util.asm.ClassVisitor;
import org.ow2.util.asm.MethodVisitor;
import org.ow2.util.asm.commons.AdviceAdapter;


class JdbcClassAdapter extends ClassAdapter {
	enum JdbcClassType {
		CONNECTION, STATEMENT, PREPARED_STATMENT
	}
	
	private static final String CONNECTION_CLAZZ = 
		JdbcTracingConstants.CONNECTION_CLAZZ.replace(".", "/");

	private static final String PREPARED_STATEMENT_CLAZZ = 
		JdbcTracingConstants.PREPARED_STATEMENT_CLAZZ.replace(".", "/");
	private static final String STATEMENT_CLAZZ = 
		JdbcTracingConstants.STATEMENT_CLAZZ.replace(".", "/");
	
	private String className;
	private JdbcClassType type;

	public JdbcClassAdapter(final ClassVisitor cv) {
		super(cv);
	}

	@Override
	public void visit(int version, int access, String name, String signature,
			String superName, String[] interfaces) {
		super.visit(version, access, name, signature, superName, interfaces);

		for (final String interfaceName : interfaces) {
			if (PREPARED_STATEMENT_CLAZZ.equals(interfaceName)) {
				type = JdbcClassType.PREPARED_STATMENT;
				this.className = JdbcTracingConstants.PREPARED_STATEMENT_CLAZZ;
				break;
			} else if (STATEMENT_CLAZZ.equals(interfaceName)) {
				type = JdbcClassType.STATEMENT;
				this.className = JdbcTracingConstants.STATEMENT_CLAZZ;
			} else if (CONNECTION_CLAZZ.endsWith(interfaceName)) {
				type = JdbcClassType.CONNECTION;
				break;
			}
		}
	}

	@Override
	public MethodVisitor visitMethod(final int access, final String name, final String description,
			final String signature, final String[] exceptions) {
		final MethodVisitor mv = cv.visitMethod(access, name, description, signature, exceptions);
		if (mv != null && type != null) {
			switch (type) {
			case CONNECTION:
				final Matcher connectionMethodMatcher = 
					JdbcTracingConstants.CONNECTION_METHOD_PATTERN.matcher(name);
				if (connectionMethodMatcher.matches()) {
					final AdviceAdapter adviceAdapter = 
						new ConnectionAdviceAdapter(mv, access, name, description);
					return adviceAdapter;
				}
				break;
			case PREPARED_STATMENT:
			case STATEMENT:
				final Matcher statementMethodMatcher = 
					JdbcTracingConstants.STATEMENT_METHOD_PATTERN.matcher(name);
				if (statementMethodMatcher.matches()) {
					final boolean isPreparedStatement = type == JdbcClassType.PREPARED_STATMENT;
					final AdviceAdapter adviceAdapter = 
						new StatementAdviceAdapter(mv, access, name, description, className, isPreparedStatement);
					return adviceAdapter;
				}
				break;
			}
		}
		return mv;
	}
}
